﻿
namespace SSS.AlphaNN.Logic
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using SSS.AlphaNN.Components;
    using System.Globalization;
    using System.Windows.Forms;
    public class WorkingNetworkNeuronDouble
    {

        /// <summary>
        /// Обрабатываемая рабочая область
        /// </summary>
        private WorkGround _wrkGround;

        /// <summary>
        /// Топологическая сортировка
        /// </summary>
        /// <param name="liNeurons">Список нейронов</param>
        /// <param name="groupNeuron"> Параметр группы нейронов, группы которые могут выполняться параллельно</param>
        /// <returns>Возвращает отсортированную матрицу весов</returns>
        public List<List<Neuron>> TopologySort( )
        {
            WorkGround wrkGround = _wrkGround;

            List<List<Neuron>> _groupNeuron = new List<List<Neuron>>();

            //Список обрабатываемых ссылок
            List<Link> liLinks;
            //Список нейронов, которые будем обрабатывать
            List<Neuron> liNeuron;

            //Количество нейронов
            int countNeuron = wrkGround.ListNeuron.Count;


            if (countNeuron > 0)
            {
                //группы нейронов которые сначала вычисляются
                _groupNeuron = new List<List<Neuron>>();

                //объекты которые были до обработки
                List<Dummy> liObjectBefore = new List<Dummy>();
                //первый объект
                liObjectBefore.Add(wrkGround.ListSource[0]);
                //получаем все ссылки
                liLinks = wrkGround.GetListLink;

                while (liLinks.Count > 0 && _groupNeuron.Count < countNeuron)
                {
                    //Этап первый выборка элементов у которых начальная точка есть
                    List<Link> nextElement = liLinks
                        .Where(x => liObjectBefore.Contains(x.Slot1.Owner))
                        .ToList();

                    //выбор нейронов нужных
                    List<Dummy> firstNeuron = nextElement
                        .Select(x => x.Slot2.Owner)
                        .Distinct()
                        .ToList();
                    //выбор нейронов которые являются вторыми и притом у них ведущий есть в списке первых
                    List<Dummy> secondNeuron = liLinks
                        .Where(x => firstNeuron.Contains(x.Slot2.Owner) && !liObjectBefore.Contains(x.Slot1.Owner))
                        .Select(x => x.Slot2.Owner)
                        .Distinct()
                        .ToList();
                    //таких мя исключаем
                    List<Neuron> firstWave = firstNeuron
                        .Where(x => !secondNeuron.Contains(x))
                        .Select(x => (Neuron)x)
                        .ToList();


                    liLinks = liLinks
                        .Where(x => !(liObjectBefore.Contains(x.Slot1.Owner)))
                        .Where(x => (x.Slot2.Owner is Neuron) ? (!(firstWave.Contains((Neuron)x.Slot2.Owner))) : false)
                        .ToList();


                    liObjectBefore = firstWave
                        .Select(x => (Dummy)x)
                        .ToList();


                    _groupNeuron.Add(firstWave);
                }
            }
            return _groupNeuron;
        }

        public TypeWeight[][] CreateMatrix()
        {
            WorkGround wrkGround = _wrkGround;
            List<Neuron> liNeuron = _wrkGround.ListNeuron;
            //количество строк в матрице
            int countRows = wrkGround.ListSource[0].countSignal + wrkGround.ListNeuron.Count;
            //Количество нейронов
            int countNeuron = wrkGround.ListNeuron.Count;

            //количество входных сигналов
            int countSignal = wrkGround.ListSource[0].countSignal;

            //матрица для вычислений
            TypeWeight[][] topologyMatrix = new TypeWeight[countRows][];

            List<Link> linksThis = wrkGround.GetListLink;
            //инициализация матрицы для вычислений
            for (int i = 0; i < countRows; i++)
            {
                int dummy;
                topologyMatrix[i] = new TypeWeight[countNeuron + 1];
                Func<Dummy, bool> func = x => true;

                if (i < countSignal)
                {

                    func = x =>
                                 (x is Source);

                }
                else
                {
                    dummy = liNeuron[i - countSignal].Identificator;

                    func = x => x.Identificator == dummy;
                }

                for (int j = 0; j < countNeuron + 1; j++)
                {
                    Link l;
                    if (j < countNeuron)
                    {
                        l =
                            linksThis
                                .Find(
                                x =>
                                func(x.componentSlot1)
                                && (x.componentSlot2.Identificator == liNeuron[j].Identificator));
                    }
                    else
                    {
                        l =
                           linksThis
                               .Find(
                               x =>
                               func(x.componentSlot1)
                               && (x.componentSlot2 is Sink));
                    }

                    if (l != null)
                    {
                        topologyMatrix[i][j] = new TypeWeight() { Value = l.ValueLink, ValueSet = true, LinkDestation = l };
                        linksThis.Remove(l);
                    }
                    else
                    {
                        topologyMatrix[i][j] = new TypeWeight() { Value = 0, ValueSet = false, LinkDestation = null };
                    }
                }

            }

            return topologyMatrix;
        }

        /// <summary>
        /// Делаем массив функций активации
        /// </summary>
        /// <param name="liNe">Список нейронов</param>
        /// <returns>массив функций активации</returns>
        protected Func<double, double>[] ActivatorFunction(List<Neuron> liNe)
        {
            return liNe.Select(x =>
            {
                Application.CurrentCulture = CultureInfo.InvariantCulture;
                Func<double, double> f = y => y;
                switch (x.IdActivationFunction)
                {
                    case 0:
                        //функция активации "эквивалентность" (линейная)
                        f = y =>
                            Mathematic.EqualsFunctions(
                                double.Parse(x.ParametersForActivationFunction[0].ToString()), y);
                        break;
                    case 1:
                        f = y =>
                        {

                            //функция активации нелинейная
                            return Mathematic.NotLinedFunctionActivision(
                                double.Parse(x.ParametersForActivationFunction[0].ToString()),
                                double.Parse(x.ParametersForActivationFunction[1].ToString()),
                                double.Parse(x.ParametersForActivationFunction[2].ToString()),
                                y);
                        }
                ;
                        break;
                    case 2:
                        //функция активации униполярная
                        f = y =>
                            Mathematic.UnipolarFunction(double.Parse(x.ParametersForActivationFunction[0].ToString()), y);
                        break;
                    case 3:
                        //функция активации биполярная
                        f = y => Mathematic.BipolarFunction(Convert.ToDouble(x.ParametersForActivationFunction[0]), y);
                        break;
                    default:
                        break;
                }
                return f;
            }).ToArray();
        }

        /// <summary>
        /// Метод для создания анимации в потоке
        /// </summary>
        private void CreateAnimation()
        {
            
        }

        public List<List<double>> RunNeuronNetwork( List<List<Neuron>> groupNeuron)
        {


            WorkGround wrkGround = _wrkGround;
            List<Neuron> liNe = new List<Neuron>();
            List<List<double>> liOutput = new List<List<double>>();

            
                if (_wrkGround.ListNeuron.Count > 0 && !_wrkGround.linkConnectionSinkAndSource)
                {
                    //Мы получили список нейронов упорядоченный
                   
                    
                    groupNeuron.ForEach(x => liNe.AddRange(x));


                    // создаём матрицу весов
                    TypeWeight[][] a = CreateMatrix();
                    //инициализируем функции активации для нейронов, так как у нас сеть гетерогенная
                    Func<double, double>[] funcs = ActivatorFunction(liNe);
                    //выбираем начальные смещения у нейронов
                    double[] firstValueNeuron = liNe.Select(x => x.Offset).ToArray();
                    //инициализируем нейронную сеть. С ней мы будем работать
                    NeuronNetwork nn = new NeuronNetwork(funcs, firstValueNeuron);
                    //выборка источника
                    Source ss = wrkGround.ListSource[0];
                    //выборка данных из источника
                    List<List<double>> liInput = ss.UploadDataToTempFile();
                    for (int j = 0; j < liInput.Count; j++)
                    {
                        //запускаем нейронную сеть по данным сигналам

                        TypeWeight[][] tt = new TypeWeight[a.Length][];
                        for (int i1 = 0; i1 < tt.Length; i1++)
                        {
                            tt[i1] = new TypeWeight[a[0].Length];

                            for (int j1 = 0; j1 < tt[i1].Length; j1++)
                            {
                                tt[i1][j1] = a[i1][j1];
                            }
                        }


                        liOutput.Add(nn.Run(tt, liInput[j].ToArray()).ToList());
                    }
                    if (wrkGround.ListSink[0].PathFileDb != null && wrkGround.ListSink[0].PathFileDb != String.Empty)
                        wrkGround.ListSink[0].SaveData(liOutput);
                }
                else
                {

                    Source ss = wrkGround.ListSource[0];
                    //выборка данных из источника
                    List<List<double>> liInput = ss.UploadDataToTempFile();

                    liOutput = new List<List<double>>(liInput);
                    if (wrkGround.ListSink[0].PathFileDb != null && wrkGround.ListSink[0].PathFileDb != String.Empty)
                        wrkGround.ListSink[0].SaveData(liOutput);

                }
           

            return liOutput;
        }

        public void LearningNetwork(List<List<Neuron>> groupNeuron)
        {
           
                var aLi = groupNeuron;
                List<Neuron> liNeurons =new List<Neuron>();
                aLi.ForEach(x => liNeurons.AddRange(x));

                Source src = _wrkGround.ListSource[0];
                Teacher tt = _wrkGround.GetListTeacher;

                int eps = tt.epsilon;
                int counter = tt.countLearning;
                List<List<double>> liInputData = src.UploadDataToTempFile();

                List<List<double>> liInputDataTrue = tt.UploadDataToTempFile();

                for (int i = 0; i < liInputData.Count; i++)
                {
                    // создаём матрицу весов
                    TypeWeight[][] a = CreateMatrix();
                    //инициализируем функции активации для нейронов, так как у нас сеть гетерогенная
                    Func<double, double>[] funcs = ActivatorFunction(liNeurons);
                    //выбираем начальные смещения у нейронов
                    double[] firstValueNeuron = liNeurons.Select(x => x.Offset).ToArray();
                    //инициализируем нейронную сеть. С ней мы будем работать
                    NeuronNetwork nn = new NeuronNetwork(funcs, firstValueNeuron);

                    TypeWeight[][] cloneA = CloneArray(a);

                    List<double> resMiddle = nn.Run(cloneA, liInputData[i].ToArray()).ToList();

                    for (int j = 0; j < resMiddle.Count; j++)
                    {
                        resMiddle[j] -= liInputDataTrue[i][j];
                    }
                    double diff = resMiddle.Select(x => Math.Abs(x)).Max();

                    if (diff < Math.Abs(Math.Pow(10, -eps)))
                        break;
                    else
                    {
                       
                        int index = 0;

                        Func<List<double>, double> funcOptimum = (x) =>
                        {

                            TypeWeight[][] cloneA1 = CloneArray(a);
                            int k1 = 0;
                            for (int k = 0; k < cloneA1.Length; k++)
                            {
                                if (cloneA1[k][index].ValueSet)
                                {
                                    cloneA1[k][index].Value = x[k1];
                                    k1++;
                                }
                            }

                            //инициализируем нейронную сеть. С ней мы будем работать
                            NeuronNetwork nn1 = new NeuronNetwork(funcs, firstValueNeuron);
                            List<double> resMiddle1 = nn.Run(cloneA1, liInputData[i].ToArray()).ToList();

                            for (int j = 0; j < resMiddle1.Count; resMiddle1[j] -= liInputDataTrue[i][j++])
                                ;

                            double diff1 = resMiddle1.Select(z => Math.Abs(z)).Max();
                            return diff1;
                        };

                        while (diff > Math.Abs(Math.Pow(10, -eps)) && counter > 0)
                        {
                            double diffFirst = diff;
                            List<double> input = a
                                                .Where(x => x[index].ValueSet)
                                                .Select(x => x[index].Value)
                                                .ToList();
                            List<double> liMaximum = ForGA.GARun( input, funcOptimum);

                            Action<TypeWeight[][]> replaceArrayItem = x =>
                                                                          {
                                                                              int k1 = 0;
                                                                              for (int k = 0; k < x.Length; k++)
                                                                              {
                                                                                  if (x[k][index].ValueSet)
                                                                                  {
                                                                                      x[k][index].Value = liMaximum[k1];
                                                                                      k1++;
                                                                                  }
                                                                              }
                                                                          };

                            cloneA = CloneArray(a);
                            replaceArrayItem(cloneA);
                            resMiddle = nn.Run(cloneA, liInputData[i].ToArray()).ToList();

                            for (int j = 0; j < resMiddle.Count; resMiddle[j] -= liInputDataTrue[i][j++])
                                ;
                            diff = resMiddle.Select(x => Math.Abs(x)).Max();

                            if (diffFirst > diff)
                            {
                                replaceArrayItem(a);
                                for(int i1=0;i1<a.Length;i1++)
                                {
                                    for(int j1=0;j1<a[0].Length;j1++)
                                    {
                                        if (a[i1][j1].ValueSet)
                                        a[i1][j1].LinkDestation.ValueLink = a[i1][j1].Value;
                                    }
                                }
                            }

                            counter--;
                            index++;
                            if (index >= a[0].Length)
                                index = 0;
                        }


                    }
                }



           
        }

        private TypeWeight[][] CloneArray(TypeWeight[][] a)
        {
            TypeWeight[][] cloneA = new TypeWeight[a.Length][];
            for (int i1 = 0; i1 < cloneA.Length; i1++)
            {
                cloneA[i1] = new TypeWeight[a[0].Length];

                for (int j1 = 0; j1 < cloneA[i1].Length; j1++)
                {
                    cloneA[i1][j1] = a[i1][j1];
                }
            }

            return cloneA;
        }
        private Func<double, double>[] GetDiffForFunctions(List<Neuron> neurons)
        {
            return neurons.Select(x =>
            {
                Application.CurrentCulture = CultureInfo.InvariantCulture;
                Func<double, double> f = y => y;
                switch (x.IdActivationFunction)
                {
                    case 0:
                        //функция активации "эквивалентность" (линейная)
                        f = y =>
                                 Convert.ToDouble(x.ParametersForActivationFunction[0]);
                        break;
                    case 1:
                        f = y =>
                        {

                            //функция активации нелинейная
                            return Mathematic.NotLinedFunctionActivision(
                                double.Parse(x.ParametersForActivationFunction[0].ToString()),
                                double.Parse(x.ParametersForActivationFunction[1].ToString()),
                                double.Parse(x.ParametersForActivationFunction[2].ToString()),
                                y);
                        }
                ;
                        break;
                    case 2:
                        //функция активации униполярная
                        f = y =>
                            Mathematic.DiffUnipolarFunction(Convert.ToDouble(x.ParametersForActivationFunction[0]), y);
                        break;
                    case 3:
                        //функция активации биполярная
                        f = y => Mathematic.DiffBipolarFunction(Convert.ToDouble(x.ParametersForActivationFunction[0]), y);
                        break;
                    default:
                        break;
                }
                return f;
            })
            .Reverse()
            .ToArray();
        }

        public WorkingNetworkNeuronDouble(WorkGround wrkGround)
        {
            _wrkGround = wrkGround;
        }
    }
}
